#include "stdafx.h"

#include "include/Gs.h"
#include "../Context.h"
#include "../Object.h"
#include "../String.h"
#include "../Value.h"
#include "../GarbageCollector.h"
#include "../Function.h"
#include "../String.h"

namespace Gs
{
	// Creates a new context.
	GS_EXPIMP IContext *GS_API CreateContext()
	{
		return new Context();
	}

	// Destroys the instance.
	IContext::~IContext()
	{
	}

	// Initialises a new instance of the IContext class.
	Context::Context()
		: _garbage(new GarbageCollector()),
		_stack(new Value[DefaultStackSize]),
		_stackFrame(-1),
		_stackTop(-1),
		_stackSize(DefaultStackSize),
		_global(new Object())
	{
		PushFrame();
	}

	// Destroys the instance.
	Context::~Context()
	{
		delete [] _stack;

		// all managed objects are deleted
		delete _garbage;
	}

	// Creates an unreferenced object.
	IObject *Context::CreateObject()
	{
		Object *obj = new Object();
		_garbage->AddManagedObject(obj);
		return obj;
	}

	// Creates an unreferenced string.
	IString *Context::CreateString(const char *str)
	{
		if (!str)
			throw InvalidParameterException("str", "valid non-null string");
		String *s = new String(str);
		_garbage->AddManagedObject(s);
		return s;
	}

	// Creates an unreferenced string.
	String *Context::CreateString(unsigned int size)
	{
		String *s = new String(size);
		_garbage->AddManagedObject(s);
		return s;
	}

	// Creates an unreferenced function.
	Function *Context::CreateFunction(NativeFunction f)
	{
		Function *func = new Function(f);
		_garbage->AddManagedObject(func);
		return func;
	}

	// Gets a pointer to the global object.
	IObject *Context::GetGlobalObject() const
	{
		return _global;
	}

	// Pops and assigns an object to a name in the global object.
	void Context::SetGlobal(const char *name)
	{
		SetGlobal(CreateString(name));
	}

	// Pops and assigns an object to a name in the global object.
	void Context::SetGlobal(IString *name)
	{
		IValue *val = ValueAt();
		_global->SetValue((String *)name, *(Value *)val);
		Pop();
	}

	// Pushes a global value onto the stack.
	void Context::PushGlobal(const char *name)
	{
		PushGlobal(CreateString(name));
	}

	// Pushes a global value onto the stack.
	void Context::PushGlobal(IString *name)
	{
		IValue *val = _global->GetField(name);
		Push(val);
	}

	// Compiles a script into a function on the stack using a file.
	void Context::CompileScriptFromFile(const char *filename)
	{
		if (!filename)
			throw InvalidParameterException("filename", "A valid filename");
		FILE *file = fopen(filename, "r");
		if (!file)
			throw FileNotFoundException(filename);

		fseek(file, 0, SEEK_END);
		long size = ftell(file);
		fseek(file, 0, SEEK_SET);

		String *script = CreateString(size + 1);
		size_t sz = fread(script->GetBuffer(), 1, size, file);
		script->GetBuffer()[sz] = 0;

		CompileScript(script->GetBuffer(), filename);
	}

	// Calls the function on the stack. The function should be pushed before arguments.
	void Context::Call(int arguments)
	{
		Value *val = (Value *)ValueAt(-1 - arguments);
		if (val->GetType() != FunctionType)
			throw InvalidOperationException("The value at the required index in the stack is not a function.");

		Function *func = val->ToFunction();
		if (func->IsNativeFunction())
		{
			// temporarily correct stack frame for call
			int frame = _stackFrame;
			_stackFrame = -1 - arguments;

			func->GetNativeFunction()(this);

			_stackFrame = frame;
		}
	}
}